/*

 * Copyright (C) 2021 Huawei Device Co., Ltd.

 * Licensed under the Apache License, Version 2.0 (the "License");

 * you may not use this file except in compliance with the License.

 * You may obtain a copy of the License at

 *

 *     http://www.apache.org/licenses/LICENSE-2.0

 *

 * Unless required by applicable law or agreed to in writing, software

 * distributed under the License is distributed on an "AS IS" BASIS,

 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

 * See the License for the specific language governing permissions and

 * limitations under the License.

 */
package com.fxn.component;

import com.futuremind.recyclerviewfastscroll.FastScroller;

import com.fxn.pix.ResourceTable;
import com.fxn.interfaces.BottomSheetBehavior;
import com.fxn.interfaces.BottomSheetCallBack;
import com.fxn.adapters.InstantImageAdapter;
import com.fxn.adapters.MainImageAdapter;
import com.fxn.utils.PixelUtil;

import ohos.agp.components.ListContainer;
import ohos.agp.components.StackLayout;
import ohos.agp.components.DependentLayout;
import ohos.agp.components.Image;
import ohos.agp.components.Text;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.LayoutScatter;
import ohos.agp.components.ComponentContainer;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;

import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.eventhandler.InnerEvent;

import ohos.multimodalinput.event.TouchEvent;

/**
 * description : 自定义抽屉布局
 *
 * @since 2021-04-15
 */
public class MyBottomSheet extends StackLayout implements Component.TouchEventListener {
    private static final int FOLD = 100;
    private static final int TOP = 200;
    private static final int AUTO_SCROLL_DELAY = 25;

    private ListContainer horizontalList;
    private ListContainer verticalList;
    private Context mContext;
    private LayoutScatter mLayoutScatter;
    private double offsetY = 0;
    private double oldY = 0;
    private double newY = 0;
    private MyBottomSheet self;
    private int minHeight = 0;
    private int maxHeight = PixelUtil.screenHeight();
    private Status mStatus = Status.ON_FOLD;
    private StackLayout topbar;
    private Image selection_back;
    private Image selection_check;
    private Image arrow_up;
    private Text selection_count;
    private Text selection_ok;
    private FastScroller fastHandle;
    private DependentLayout verticalListRoot;
    private BottomSheetBehavior bottomSheetBehavior;
    private BottomSheetCallBack mBottomSheetCallBack;

    // 创建一个自动控制的EventRunner
    private EventRunner eventRunner = EventRunner.getMainEventRunner();
    private MyEventHandler autoScrollHandler = new MyEventHandler(eventRunner);
    private Runnable autoScrollRunnable = new Runnable() {
        @Override
        public void run() {
            StackLayout.LayoutConfig layoutConfig = (LayoutConfig) self.getLayoutConfig();
            int height = layoutConfig.height;
            if (layoutConfig.height - offsetY >= maxHeight) {
                height = maxHeight;
                mStatus = Status.ON_TOP;
                if (mBottomSheetCallBack != null) {
                    mBottomSheetCallBack.onTop();
                }
            } else if (layoutConfig.height - offsetY <= minHeight) {
                height = minHeight;
                mStatus = Status.ON_FOLD;
                if (mBottomSheetCallBack != null) {
                    mBottomSheetCallBack.onFold();
                }
            } else {
                height -= offsetY;
                mStatus = Status.ON_DRAG;
                if (mBottomSheetCallBack != null) {
                    mBottomSheetCallBack.onDrag();
                }
            }
            layoutConfig.height = height;
            self.setLayoutConfig(layoutConfig);
        }
    };


    /**
     * 初始化MyBottomSheet
     *
     * @param context context
     * @throws RuntimeException RuntimeException : "minHeight must be set!"
     */
    public MyBottomSheet(Context context) throws RuntimeException {
        super(context);
        this.mContext = context;
        setTouchEventListener(this);
        initView();
    }

    /**
     * 初始化MyBottomSheet
     *
     * @param context context
     * @param attrSet attrSet
     * @throws RuntimeException RuntimeException : "minHeight must be set!"
     */
    public MyBottomSheet(Context context, AttrSet attrSet) throws RuntimeException {
        super(context, attrSet);
        this.mContext = context;
        setTouchEventListener(this);
        initView();
    }

    /**
     * 初始化MyBottomSheet
     *
     * @param context   context
     * @param attrSet   attrSet
     * @param styleName styleName
     * @throws RuntimeException RuntimeException : "minHeight must be set!"
     */
    public MyBottomSheet(Context context, AttrSet attrSet, String styleName) throws RuntimeException {
        super(context, attrSet, styleName);
        this.mContext = context;
        setTouchEventListener(this);
        initView();
    }

    private void initView() throws RuntimeException {
        ComponentContainer container;
        mLayoutScatter = LayoutScatter.getInstance(mContext);
        if (mLayoutScatter.parse(ResourceTable.Layout_bottom_sheet, null, false) instanceof ComponentContainer) {
            container = (ComponentContainer) mLayoutScatter.parse(ResourceTable.Layout_bottom_sheet, null, false);
            findComponentsById(container);
            addComponent(container);
            minHeight = getMinHeight();
            if (minHeight <= 0) {
                throw new SecurityException("minHeight must be set!");
            }
            self = MyBottomSheet.this;
            self.setLayoutRefreshedListener(new MyLayoutRefreshedListener());
        }
    }

    private class MyLayoutRefreshedListener implements LayoutRefreshedListener {

        @Override
        public void onRefreshed(Component component) {
            ComponentContainer.LayoutConfig layoutConfig = self.getLayoutConfig();
            self.setAlpha((float) (layoutConfig.height - minHeight) / (maxHeight - minHeight));
            if (bottomSheetBehavior != null) {
                bottomSheetBehavior.onSlide(
                        (float) (layoutConfig.height - minHeight) / (maxHeight - minHeight));
            }
        }
    }

    private void findComponentsById(ComponentContainer container) {
        if (container.findComponentById(ResourceTable.Id_horizontal_list) instanceof ListContainer) {
            horizontalList = (ListContainer) container.findComponentById(ResourceTable.Id_horizontal_list);
        }
        if (container.findComponentById(ResourceTable.Id_vertical_list) instanceof ListContainer) {
            verticalList = (ListContainer) container.findComponentById(ResourceTable.Id_vertical_list);
        }
        if (container.findComponentById(ResourceTable.Id_top_bar) instanceof StackLayout) {
            topbar = (StackLayout) container.findComponentById(ResourceTable.Id_top_bar);
        }
        if (container.findComponentById(ResourceTable.Id_selection_count) instanceof Text) {
            selection_count = (Text) container.findComponentById(ResourceTable.Id_selection_count);
        }
        if (container.findComponentById(ResourceTable.Id_selection_back) instanceof Image) {
            selection_back = (Image) container.findComponentById(ResourceTable.Id_selection_back);
        }
        if (container.findComponentById(ResourceTable.Id_selection_check) instanceof Image) {
            selection_check = (Image) container.findComponentById(ResourceTable.Id_selection_check);
        }
        if (container.findComponentById(ResourceTable.Id_selection_ok) instanceof Text) {
            selection_ok = (Text) container.findComponentById(ResourceTable.Id_selection_ok);
        }
        if (container.findComponentById(ResourceTable.Id_arrow_up) instanceof Image) {
            arrow_up = (Image) container.findComponentById(ResourceTable.Id_arrow_up);
        }
        if (container.findComponentById(ResourceTable.Id_fast_handler) instanceof FastScroller) {
            fastHandle = (FastScroller) container.findComponentById(ResourceTable.Id_fast_handler);
        }
        if (container.findComponentById(ResourceTable.Id_dependent_layout) instanceof DependentLayout) {
            verticalListRoot = (DependentLayout) container.findComponentById(ResourceTable.Id_dependent_layout);
        }
    }

    /**
     * 设置MyBottomSheet的最大高度
     *
     * @param providerHeight 此布局包含的listContainer的provider的高度
     */
    public void setMaxHeight(int providerHeight) {
        int screenHeight = DisplayManager.getInstance().getDefaultDisplay(mContext).get().getAttributes().height;
        ComponentContainer.LayoutConfig layoutConfig = verticalListRoot.getLayoutConfig();
        layoutConfig.height = Math.min(screenHeight - topbar.getLayoutConfig().height, providerHeight);
        verticalListRoot.setLayoutConfig(layoutConfig);
        this.maxHeight = Math.min(screenHeight, providerHeight + topbar.getLayoutConfig().height);
    }

    /**
     * 缩小高度至折叠状态
     */
    public void gotoFold() {
        autoScrollHandler.sendEvent(FOLD);
    }

    /**
     * 获取MyBottomSheet包含的竖直方向的 listContainer
     *
     * @return MyBottomSheet包含的竖直方向的 listContainer
     */
    public ListContainer getVerticalList() {
        return verticalList;
    }

    /**
     * 获取MyBottomSheet包含的水平方向的 listContainer
     *
     * @return MyBottomSheet包含的水平方向的 listContainer
     */
    public ListContainer getHorizontalList() {
        return horizontalList;
    }

    /**
     * 获取MyBottomSheet包含的topbar
     *
     * @return MyBottomSheet包含的topbar
     */
    public StackLayout getTopbar() {
        return topbar;
    }

    /**
     * 获取MyBottomSheet包含的 selection_back
     *
     * @return MyBottomSheet包含的Selection_back
     */
    public Image getSelectionBack() {
        return selection_back;
    }

    /**
     * 获取MyBottomSheet包含的 selection_check
     *
     * @return MyBottomSheet包含的 selection_check
     */
    public Image getSelectionCheck() {
        return selection_check;
    }

    /**
     * 获取MyBottomSheet包含的 selection_count
     *
     * @return MyBottomSheet包含的 selection_count
     */
    public Text getSelectionCount() {
        return selection_count;
    }

    /**
     * 获取MyBottomSheet包含的 selection_ok
     *
     * @return MyBottomSheet包含的 selection_ok
     */
    public Text getSelectionOk() {
        return selection_ok;
    }

    /**
     * add BottomSheetCallBack
     *
     * @param bottomSheetCallBack bottomSheetCallBack
     */
    public void addBottomSheetCallBack(BottomSheetCallBack bottomSheetCallBack) {
        mBottomSheetCallBack = bottomSheetCallBack;
    }

    /**
     * 获取MyBottomSheet包含的 arrow_up
     *
     * @return MyBottomSheet包含的 arrow_up
     */
    public Image getArrowUp() {
        return arrow_up;
    }

    /**
     * 设置 MyBottomSheet 滑动的监听
     *
     * @param bottomSheetBehavior MyBottomSheet 滑动的监听
     */
    public void setBottomSheetBehavior(BottomSheetBehavior bottomSheetBehavior) {
        this.bottomSheetBehavior = bottomSheetBehavior;
    }

    /**
     * 设置MyBottomSheet包含的水平和竖直方向的provider
     *
     * @param instantImageAdapter 水平方向的provider
     * @param mainImageAdapter    竖直方向的provider
     */
    public void setItemProvider(InstantImageAdapter instantImageAdapter, MainImageAdapter mainImageAdapter) {
        horizontalList.setItemProvider(instantImageAdapter);
        verticalList.setItemProvider(mainImageAdapter);
        fastHandle.setViewProvider(new CustomScrollerViewProvider());
        // 必须在 listContainer.setItemProvider之后调用
        fastHandle.setListContainer(verticalList);
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                autoScrollHandler.removeAllEvent();
                oldY = touchEvent.getPointerScreenPosition(touchEvent.getIndex()).getY();
                break;
            case TouchEvent.POINT_MOVE:
                newY = touchEvent.getPointerScreenPosition(touchEvent.getIndex()).getY();
                offsetY = newY - oldY;
                oldY = newY;
                if (mStatus == Status.ON_FOLD) {
                    // 折叠状态
                    if (offsetY < 0) {
                        autoScrollHandler.postSyncTask(autoScrollRunnable);
                    }
                    return true;
                }
                if (mStatus == Status.ON_TOP) {
                    // 展开状态
                    // 如果是向下滑动就拦截，否则不拦截
                    if (offsetY > 0 && verticalList.getItemPosByVisibleIndex(0) == 0) {
                        // 下滑
                        autoScrollHandler.postSyncTask(autoScrollRunnable);
                    }
                    return true;
                }
                if (mStatus == Status.ON_DRAG) {
                    // 拖拽状态
                    horizontalList.setTouchFocusable(false);
                    autoScrollHandler.postTask(autoScrollRunnable);
                    return true;
                }
                break;
            case TouchEvent.PRIMARY_POINT_UP:
                if ((mStatus == Status.ON_TOP || mStatus == Status.ON_FOLD) && offsetY == 0) {
                    return false;
                }
                if (mBottomSheetCallBack != null) {
                    mBottomSheetCallBack.onFlying();
                }
                mStatus = Status.ON_FLYING;
                if (offsetY > 0) {
                    if (verticalList.getItemPosByVisibleIndex(0) != 0) {
                        return false;
                    }
                    // 下滑 fold
                    autoScrollHandler.sendEvent(FOLD);
                } else {
                    autoScrollHandler.sendEvent(TOP);
                }
                break;
        }
        return true;
    }

    /**
     * 获取MyBottomSheet的状态
     *
     * @return MyBottomSheet 的状态
     */
    public Status getStatus() {
        return mStatus;
    }

    /**
     * MyBottomSheet 的状态 enum
     */
    public enum Status {
        ON_DRAG,
        ON_TOP,
        ON_FOLD,
        ON_FLYING
    }

    private class MyEventHandler extends EventHandler {
        private MyEventHandler(EventRunner runner) {
            super(runner);
        }

        // 重写实现processEvent方法
        @Override
        public void processEvent(InnerEvent event) {
            super.processEvent(event);
            StackLayout.LayoutConfig layoutConfig = null;
            int height = 0;
            if (self.getLayoutConfig() instanceof LayoutConfig) {
                layoutConfig = (LayoutConfig) self.getLayoutConfig();
                height = layoutConfig.height;
            }
            switch (event.eventId) {
                case FOLD:
                    if (minHeight < height - 100) {
                        height -= 100;
                        layoutConfig.height = height;
                        self.setLayoutConfig(layoutConfig);
                        autoScrollHandler.sendEvent(FOLD, 5);
                    }
                    if (minHeight >= height - 100) {
                        layoutConfig.height = minHeight;
                        self.setLayoutConfig(layoutConfig);
                        mStatus = Status.ON_FOLD;
                        if (mBottomSheetCallBack != null) {
                            mBottomSheetCallBack.onFold();
                        }
                    }
                    break;
                case TOP:
                    if (maxHeight > height + 100) {
                        height += 100;
                        layoutConfig.height = height;
                        self.setLayoutConfig(layoutConfig);
                        autoScrollHandler.sendEvent(TOP, 5);
                    }
                    if (maxHeight <= height + 100) {
                        layoutConfig.height = maxHeight;
                        self.setLayoutConfig(layoutConfig);
                        mStatus = Status.ON_TOP;
                        if (mBottomSheetCallBack != null) {
                            mBottomSheetCallBack.onTop();
                        }
                    }
                    break;
            }
        }
    }
}
